import WebGl from '@/modules/three';

const { Three, TWEEN, dat, Stats, OrbitControls, TextGeometry, FontLoader } =
  WebGl;

class WebGlControl {
  private container: any;
  private camera: any;
  private scene: any;
  private renderer: any;
  private stats: any;
  private animateFrame: any;
  private tween: any;
  private controls: any;
  private clock: any = new Three.Clock();

  private Geometrys: {
    cylGeometry?: any;
    stickGeo?: any;
    textGeometry?: any;
  } = {}; //保存所有Geometry，方便统一销毁
  private Materials: {
    cylMaterial?: any;
    stickMater?: any;
    textMaterial?: any;
  } = {}; //保存所有Material，方便统一销毁
  private Meshs: {
    cylMesh?: any;
    sphereMesh?: any;
  } = {};
  private sticksGroup: any;

  private raycaster: any;
  private pointer: any;
  private mouseData: any = {
    mouseX: 0,
    mouseY: 0,
  };
  private GuiInit: any = null;
  private textColors = [
    new Three.Color('#0be8df'),
    new Three.Color('#1ce80b'),
    new Three.Color('#fff741'),
  ];
  private guiData: any = {
    rotateSpeed: 3,
    color: '#21032d',
    colorText: '#0be8df',
    cylRadius: 3,
    currentAngle: 0, //当前旋转的位置。
    showBall: false, //显示测试小球
  };
  private CydParams = {
    color: 0xd2d2ed,
    transmission: 0.6,
    opacity: 0.9,
    metalness: 0,
    roughness: 0.3,
    ior: 1.3,
    thickness: 0.01,
    specularIntensity: 0.4,
    specularColor: 0xf3eafa,
    envMapIntensity: 0.43,
    lightIntensity: 1,
    exposure: 1,
  };

  public init = (id: string) => {
    // if(this.scene) {return}
    this.container = document.getElementById(id);
    let width = this.container.clientWidth,
      height = this.container.clientHeight;

    this.scene = new Three.Scene();

    this.camera = new Three.PerspectiveCamera(60, width / height, 1, 300);
    this.camera.position.x = 0;
    this.camera.position.y = 15;
    this.camera.position.z = 10;
    this.camera.lookAt(this.scene.position);

    this.renderer = new Three.WebGLRenderer();
    // this.renderer.setClearColor( 0xffffff );
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width, height);
    this.renderer.shadowMap.enabled = true;
    this.container.appendChild(this.renderer.domElement);

    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true; //阻尼
    this.controls.dampingFactor = 0.1; //阻尼

    this.raycaster = new Three.Raycaster();
    this.pointer = new Three.Vector2(-2, -2); //初始化在[-1,1]以外

    this.addThings();
    this.addLight(); //+光源
    this.animate(); //开始动
    this.initStats();
    this.initEventListen();

    this.initGui();
  };
  public destroy = () => {
    this.toggleGui(0);
    Object.values(this.Geometrys).forEach((e) => {
      e.dispose();
    });
    Object.values(this.Materials).forEach((e) => {
      e.dispose();
    });
    this.scene.clear();
    this.renderer.dispose();
    this.renderer.forceContextLoss();
    this.camera = null;
    this.scene = null;
    this.renderer = null;
    this.GuiInit = null;
    this.stats = null;
    if (this.tween) {
      this.tween.stop();
      this.tween = null;
    }
    cancelAnimationFrame(this.animateFrame);
  };

  private clickState: string = '';
  private initEventListen = () => {
    this.container.addEventListener('pointermove', this.onPointerMove);
    this.container.addEventListener(
      'mousedown',
      () => {
        this.clickState = 'down';
      },
      false,
    );
    this.container.addEventListener('mousemove', () => {}, false);
    this.container.addEventListener(
      'mouseup',
      () => {
        if (this.clickState == 'down') {
          this.clickState = '';
          // this.onPointClick();
        }
      },
      false,
    );
    // window.addEventListener('resize', this.onWindowResize);
  };
  private onPointerMove = (event) => {
    if (event.isPrimary === false) return;
    // this.mouseData.mouseX = event.clientX - this.container.clientWidth;
    // this.mouseData.mouseY = event.clientY - this.container.clientHeight;
    this.mouseData.mouseX = event.clientX - this.container.offsetLeft;
    this.mouseData.mouseY = event.clientY - this.container.offsetTop;
    var scrollTop = window.pageYOffset || document.documentElement.scrollTop; //在container中的位置要考虑scroll

    // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
    this.pointer.x =
      (this.mouseData.mouseX / this.container.clientWidth) * 2 - 1;
    this.pointer.y =
      -((this.mouseData.mouseY + scrollTop) / this.container.clientHeight) * 2 +
      1;
    // console.log('this.pointer', this.pointer)
  };

  private initStats = () => {
    this.stats = new Stats();
    this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
    this.stats.dom.style.position = 'absolute';
    this.container.appendChild(this.stats.dom);
  };

  public initGui = () => {
    if (this.GuiInit) {
      this.toggleGui();
      return;
    }
    this.GuiInit = new dat.GUI({
      // autoPlace: false
    });

    this.GuiInit.domElement.style.position = 'absolute';
    this.GuiInit.domElement.style.right = '10px';
    this.GuiInit.domElement.style.top = '10px';

    let GuiController = this.GuiInit.addFolder('控制窗口');
    GuiController.add(this.guiData, 'rotateSpeed', 2, 15, 0.5).name('旋转速度');
    GuiController.addColor(this.guiData, 'color').name('背景颜色');
    GuiController.add(this.guiData, 'showBall').name('测试小球');
    GuiController.open();

    this.addCydGui();
    // let cubeGui = this.GuiInit.addFolder("cube position");
    // cubeGui.open()
  };
  private addCydGui = () => {
    let GuiController = this.GuiInit.addFolder('底座');
    GuiController.addColor(this.CydParams, 'color').onChange(() => {
      this.Materials.cylMaterial.color.set(this.CydParams.color);
      // render();
    });
    GuiController.add(this.CydParams, 'transmission', 0, 1, 0.01).onChange(
      () => {
        this.Materials.cylMaterial.transmission = this.CydParams.transmission;
        // render();
      },
    );
    GuiController.add(this.CydParams, 'opacity', 0, 1, 0.01).onChange(() => {
      this.Materials.cylMaterial.opacity = this.CydParams.opacity;
      // render();
    });
    GuiController.add(this.CydParams, 'metalness', 0, 1, 0.01).onChange(() => {
      this.Materials.cylMaterial.metalness = this.CydParams.metalness;
      // render();
    });
    GuiController.add(this.CydParams, 'roughness', 0, 1, 0.01).onChange(() => {
      this.Materials.cylMaterial.roughness = this.CydParams.roughness;
      // render();
    });
    GuiController.add(this.CydParams, 'ior', 1, 2, 0.01).onChange(() => {
      this.Materials.cylMaterial.ior = this.CydParams.ior;
      // render();
    });
    GuiController.add(this.CydParams, 'thickness', 0, 5, 0.01).onChange(() => {
      this.Materials.cylMaterial.thickness = this.CydParams.thickness;
      // render();
    });
    GuiController.add(this.CydParams, 'specularIntensity', 0, 1, 0.01).onChange(
      () => {
        this.Materials.cylMaterial.specularIntensity =
          this.CydParams.specularIntensity;
        // render();
      },
    );
    GuiController.addColor(this.CydParams, 'specularColor').onChange(() => {
      this.Materials.cylMaterial.specularColor.set(
        this.CydParams.specularColor,
      );
      // render();
    });
    GuiController.add(this.CydParams, 'envMapIntensity', 0, 1, 0.01)
      .name('envMapIntensity')
      .onChange(() => {
        this.Materials.cylMaterial.envMapIntensity =
          this.CydParams.envMapIntensity;
        // render();
      });
    // GuiController.add( this.CydParams, 'exposure', 0, 1, 0.01 )
    //   .onChange(() => {
    //     renderer.toneMappingExposure = this.CydParams.exposure;
    //     // render();
    //   } );
    GuiController.open();
  };

  public toggleGui = (state: any = null) => {
    if (!this.GuiInit || !this.GuiInit.domElement) {
      return;
    }
    let display = this.GuiInit.domElement.style.display == 'none' ? '' : 'none';
    if (state == 0) {
      display = 'none';
    }
    this.GuiInit.domElement.style.display = display;
  };

  private addLight = () => {
    let ambi = new Three.AmbientLight(0x686868);
    this.scene.add(ambi);
    let dirLight = new Three.SpotLight(0xffffff);
    dirLight.position.set(-3, 5, 3);
    dirLight.intensity = 0.6;
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.width = 2048; //阴影贴图宽度设置为2048像素
    dirLight.shadow.mapSize.height = 2048; //阴影贴图高度设置为2048像素
    dirLight.shadow.radius = 5; //设置阴影贴图模糊度
    this.scene.add(dirLight);
    // let light = new Three.PointLight( 0xFFFF00 );
    // light.position.set( -100, 100, 0 );
    // this.scene.add(light);
  };

  private addThings = () => {
    // const axesHelper = new Three.AxesHelper( 800 );
    // this.scene.add( axesHelper )  //添加坐标轴；红线是X轴，绿线是Y轴，蓝线是Z轴

    this.addCylinder();
    this.addSticks();
    this.addball();
    this.addText();
  };

  private addText = () => {
    /** 文字 **/
    const textLoader = new FontLoader();
    textLoader.load('../../helvetiker_regular.typeface.json', (font: any) => {
      this.Geometrys.textGeometry = new TextGeometry('Loading...', {
        font: font,
        size: 0.5,
        height: 0.1,
        curveSegments: 12,
        bevelEnabled: false,
        bevelThickness: 5,
        bevelSize: 2,
        bevelSegments: 5,
      });
      this.Geometrys.textGeometry.computeBoundingBox();
      this.Materials.textMaterial = new Three.MeshPhongMaterial({
        color: this.guiData.colorText,
        flatShading: true,
      }); // front
      const textMesh = new Three.Mesh(
        this.Geometrys.textGeometry,
        this.Materials.textMaterial,
      );
      textMesh.position.set(-1.3, 0.1, 0);
      textMesh.rotation.set(-Math.PI / 2, 0, 0);
      textMesh.receiveShadow = true;
      textMesh.castShadow = true;
      this.scene.add(textMesh);
    });
    /** 文字 **/
  };

  private addball = () => {
    const geometry = new Three.SphereGeometry(0.2, 26, 15);
    const material = new Three.MeshBasicMaterial({ color: 0xfae2c4 });
    this.Meshs.sphereMesh = new Three.Mesh(geometry, material);
    this.scene.add(this.Meshs.sphereMesh);
  };

  private addCylinder = () => {
    this.Geometrys.cylGeometry = new Three.CylinderGeometry(
      this.guiData.cylRadius,
      this.guiData.cylRadius,
      0.2,
      42,
    );
    this.Materials.cylMaterial = new Three.MeshPhysicalMaterial({
      color: this.CydParams.color,
      metalness: this.CydParams.metalness,
      roughness: this.CydParams.roughness,
      ior: this.CydParams.ior,
      // alphaMap: texture,
      // envMap: hdrEquirect,
      envMapIntensity: this.CydParams.envMapIntensity,
      transmission: this.CydParams.transmission, // use material.transmission for glass materials
      specularIntensity: this.CydParams.specularIntensity,
      specularColor: this.CydParams.specularColor,
      opacity: this.CydParams.opacity,
      // side: Three.DoubleSide,  //GL_INVALID_OPERATION: Feedback loop formed between Framebuffer and active Texture
      transparent: true,
    });
    // this.Materials.cylMaterial = new Three.MeshPhongMaterial( { color: 0xccddff, refractionRatio: 0.98, reflectivity: 0.9 } );
    // const cubeMaterial2 = new THREE.MeshPhongMaterial( { color: 0xccfffd, envMap: textureCube, refractionRatio: 0.985 } );
    // const cubeMaterial1 = new THREE.MeshPhongMaterial( { color: 0xffffff, envMap: textureCube, refractionRatio: 0.98 } );
    this.Meshs.cylMesh = new Three.Mesh(
      this.Geometrys.cylGeometry,
      this.Materials.cylMaterial,
    );
    this.Meshs.cylMesh.receiveShadow = true;
    // this.Meshs.cylMesh.castShadow = true;
    this.scene.add(this.Meshs.cylMesh);
  };
  private addSticks = () => {
    this.sticksGroup = new Three.Group();
    this.scene.add(this.sticksGroup);

    this.Geometrys.stickGeo = new Three.CapsuleGeometry(0.2, 2, 4, 20);
    const matOptions = (color = '#8360af') => {
      return {
        uniforms: {
          val: { value: 0.0 },
          color2: {
            value: new Three.Color(color),
          },
          color3: {
            value: new Three.Color('#fae2c4'),
          },
        },
        vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
        fragmentShader: `
        uniform vec3 color2;
        uniform vec3 color3;
        uniform float val;
        varying vec2 vUv;
        void main() {
          float y = vUv.y;
          // 发光的强度
          float glowIntensity = 1.5;

          vec3 co2 = color2.rgb * glowIntensity;
          vec3 co3 = color3.rgb * glowIntensity;
          vec3 gradientColor;
          if (y <= 0.5) {
            gradientColor = mix(co2, co3, ((1.0 - y) * 2.2 - 1.1) * val);
          } else {
            gradientColor = mix(co2, co3, (y * 2.2 - 1.1) * val);
          }
          gl_FragColor = vec4(gradientColor, 1.0);
        }
      `,
        // lights: true
        // wireframe: true
      };
    };
    // this.Materials.stickMater = new Three.ShaderMaterial(matOptions);

    this.sticksGroup.rotation.set(0, Math.PI / 2, 0);
    const stickCount = 15;
    for (let i = 0; i < stickCount; i++) {
      const mat = new Three.ShaderMaterial(matOptions());
      const mesh = new Three.Mesh(this.Geometrys.stickGeo, mat);
      // mesh.rotation.set(Math.PI/2,0,0)
      const radius = this.guiData.cylRadius * 0.8;
      const angle = (2 * Math.PI * i) / stickCount;
      const x = radius * Math.cos(angle);
      const z = radius * Math.sin(angle);
      mesh.position.set(x, 0, z);
      mesh.receiveShadow = true;
      mesh.castShadow = true;
      mesh.customAngle = angle;
      this.sticksGroup.add(mesh);
    }
    // console.log('this.sticksGroup', this.sticksGroup)
    // this.computScale()
  };

  private mouseAngle: number = 0;
  private tweenToAngle = (x, y) => {
    const a = new Three.Vector3(0, 0, 1);
    const b = new Three.Vector3(x - 0.5, 0, y - 0.5);
    let angle = b.angleTo(a);
    angle = x < 0.5 ? Math.PI * 2 - angle : angle;
    this.mouseAngle = angle;
  };

  private sinFun = (angle: number) => {
    return Math.round(Math.sin(angle) * 1000000) / 1000000;
  };
  private angleAddLock: boolean = false;
  private computScale = (delta) => {
    if (!this.angleAddLock) {
      this.guiData.currentAngle += delta * this.guiData.rotateSpeed;
    } else {
      this.guiData.currentAngle = this.mouseAngle;
      /** 增加阻尼效果 **/
      // const damping = 0.1;
      // let diff = 0
      // if(Math.abs(this.mouseAngle - this.guiData.currentAngle) > Math.PI) {
      //   if(this.guiData.currentAngle < this.mouseAngle) {
      //     diff = this.mouseAngle - this.guiData.currentAngle - Math.PI - Math.PI
      //   }else {
      //     diff = Math.PI + Math.PI + this.mouseAngle - this.guiData.currentAngle
      //   }
      // }else {
      //   diff = (this.mouseAngle - this.guiData.currentAngle) *damping;
      // }
      // this.guiData.currentAngle+= diff
      /** 增加阻尼效果 **/
    }
    this.guiData.currentAngle =
      this.guiData.currentAngle > Math.PI * 2 ? 0 : this.guiData.currentAngle;
    this.guiData.currentAngle =
      this.guiData.currentAngle < 0 ? Math.PI * 2 : this.guiData.currentAngle;

    // console.log('Angle:', this.guiData.currentAngle)
    this.sticksGroup.children.map((stick) => {
      const ang = stick.customAngle;
      const curAng = this.guiData.currentAngle;
      // const curAng = Math.PI*1;  //test
      let diff = this.sinFun(ang - curAng);
      // stick.scale.set(1,scaleY,1)
      diff = diff < -0.1 ? -0.1 : diff;
      stick.position.y = diff;

      stick.material.uniforms.val.value = diff < 0 ? 0 : diff;
    });

    if (this.Meshs.sphereMesh) {
      const radius = this.guiData.cylRadius * 1.3;
      const x = radius * Math.cos(this.guiData.currentAngle);
      const z = radius * Math.sin(this.guiData.currentAngle);
      this.Meshs.sphereMesh.position.set(x, 0, z);

      this.Meshs.sphereMesh.visible = this.guiData.showBall;
    }
  };

  // 使用线性插值计算颜色过渡
  private interpolateColors = (color1, color2, color3, t) => {
    // const t0 = t/3;  //0.11,0.22,0.33
    let t1 = t < 0.5 ? (0.33 - t) * 3 : (t - 0.66) * 3;
    let t2 = t <= 0.33 ? t * 3 : 2 - t * 3;
    let t3 = t <= 0.66 ? t * 3 - 0.99 : 3 - t * 3;

    t1 = Three.MathUtils.clamp(t1, 0, 1);
    t2 = Three.MathUtils.clamp(t2, 0, 1);
    t3 = Three.MathUtils.clamp(t3, 0, 1);

    const r = color1.r * t1 + color2.r * t2 + color3.r * t3;
    const g = color1.g * t1 + color2.g * t2 + color3.g * t3;
    const b = color1.b * t1 + color2.b * t2 + color3.b * t3;
    return new Three.Color(r, g, b);
  };
  private totalTime: number = 5; // 渐变总时间（秒）
  private currentTime = 0; // 定义当前时间变量
  private computeColor = (delta) => {
    // 更新当前时间
    this.currentTime += delta;
    // 根据当前时间计算颜色插值
    const t = (this.currentTime % this.totalTime) / this.totalTime; // 计算归一化时间值（0-1之间）
    this.guiData.colorText = this.interpolateColors(
      this.textColors[0],
      this.textColors[1],
      this.textColors[2],
      t,
    );
    if (this.Materials.textMaterial) {
      this.Materials.textMaterial.color.set(this.guiData.colorText);
    }
  };

  private animate = () => {
    // if(!this.renderer){return;}
    if (this.stats) {
      this.stats.begin();
    }

    this.render();
    TWEEN.update();
    this.controls.update();

    const delta = this.clock.getDelta();
    this.computScale(delta);
    this.computeColor(delta);

    if (this.stats) {
      this.stats.end();
    }
    this.animateFrame = requestAnimationFrame(this.animate);
  };
  private render = () => {
    // 射线
    this.raycaster.setFromCamera(this.pointer, this.camera);
    const intersects = this.raycaster.intersectObjects(
      [this.Meshs.cylMesh],
      false,
    );
    if (intersects[0]) {
      if (this.clickState == '') {
        this.angleAddLock = true;
        // console.log('intersects:', intersects[0].uv)  //左下角0,0 右上角1,1
        const x = intersects[0].uv.x;
        const y = intersects[0].uv.y;
        this.tweenToAngle(x, y);
      }
    } else {
      this.angleAddLock = false;
    }

    this.renderer.setClearColor(this.guiData.color, 1.0);
    this.renderer.render(this.scene, this.camera);
  };
}

const webGlControl = new WebGlControl();
export default webGlControl;
